上一篇笔记中提到了ConcurrencyMode, WCF中的并发指的是同一个服务实例上下文同时处理多个服务调用请求. 这里再进一步介绍一下ConcurrencyMode, 特别是ConcurrencyMode与InstanceContextMode之间的关系
WCF并发框架解决的是如何有效地处理被分发到同一个服务实例上下文的多个服务调用请求. 这些并行的调用请求可能来自不同的客户端(服务代理), 也可能来自相同的客户端.
Instance Management and Concurrency
Service-instance thread safety is closely related to the service instancing mode. A per-call service instance is thread-safe by definition, because each call gets its own dedicated instance. That instance is accessible only by its assigned worker thread, and because no other threads will be accessing it, it has no need for synchronization. 对实例资源不需要同步
However, a per-call service is typically state-aware. The state store can be an inmemory resource such as static dictionary, and it can be subject to multithreaded access because the service can sustain concurrent calls, whether from the same client or from multiple clients. Consequently, you must synchronize access to the state store. 对静态资源仍然需要同步
A per-session service always requires concurrency management and synchronization, because the client may use the same proxy and yet dispatch calls to the service on multiple client-side threads. 在同一个Client端发起多个线程, 但使用的是同一个Proxy. 这种情况下, 调用的是同一个Service Instance. 所以需要同步; 如果每个线程都new一个Porxy, 那么它们调用的是不同的Service Instance, 不需要对实例资源进行同步.
A singleton service is even more susceptible(敏感) to concurrent access, and must support synchronized access. The singleton has some inmemory state that all clients implicitly share. On top of the possibility of the client dispatching calls on multiple threads, as with a per-session service, a singleton may simply have multiple clients in different execution contexts, each using its own thread to call the service. All of these calls will enter the singleton on different threads from the I/O completion thread pool—hence the need for synchronization. 单例模式下, 即使是不同的Proxy, 调用的都是同一个Service Instance. 需要同步.
CurrentMode有以下三种1
2
3
4
5
6public enum ConcurrencyMode
{
Single,//默认加锁
Reentrant, //可重入
Multiple
}
ConcurrencyMode.Single
When the service is configured with ConcurrencyMode.Single, WCF will provide automatic synchronization to the service context and disallow concurrent calls by associating the context containing the service instance with a synchronization lock. Every call coming into the service must first try to acquire the lock. If the lock is unowned, the caller will be allowed in. Once the operation returns, WCF will unlock the lock, thus allowing in another caller.
The important thing is that only one caller at a time is ever allowed. If there are multiple concurrent callers while the lock is locked, the callers are all placed in a queue and are served out of the queue in order. If a call times out while blocked, WCF will remove the caller from the queue and the client will get a TimeoutException.
Because the default concurrency mode is synchronized access, the susceptible instancing modes of per-session and singleton are also synchronized by default. Note that even calls to a per-call service instance are synchronized by default.
ConcurrencyMode.Multiple
When the service is configured with ConcurrencyMode.Multiple, WCF will stay out of the way and will not synchronize access to the service instance in any way. ConcurrencyMode.Multiple simply means that the service instance is not associated with any synchronization lock, so concurrent calls are allowed on the service instance. Put differently, when a service instance is configured with ConcurrencyMode.Multiple WCF will not queue up the client messages and dispatch them to the service instance as soon as they arrive.
Multiple选项不提供任何同步措施, 需要手动同步, 例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19[]
interface IMyContract
{
void MyMethod();
}
[]
class MyService : IMyContract
{
int[] m_Numbers;
List<string> m_Names;
public void MyMethod()
{
lock(this)
{
...
}
}
}
While this code is thread-safe, you actually gain little from the use of ConcurrencyMode.Multiple: the net effect in terms of synchronization is similar to using ConcurrencyMode.Single, yet you have increased the overall code complexity and reliance on developers’ discipline. In general, you should avoid ConcurrencyMode.Multiple. However, there are cases where ConcurrencyMode.Multiple is useful.
ConcurrencyMode.Reentrant
The ConcurrencyMode.Reentrant value is a refinement of the ConcurrencyMode.Single value. Similarly to ConcurrencyMode.Single, ConcurrencyMode.Reentrantassociates the service instance with a synchronization lock, so that concurrent calls on the same instance are never allowed. However, if the reentrant service calls out to another service or a callback, and that call chain (or causality) somehow winds its way back to the service instance 如下图所示, WCF silently releases the synchronization lock that is associated with the instance.it is allowed to reenter the service instance.
ConcurrencyMode.Reentrant is designed to avoid the potential deadlock of reentrancy.
A service configured with ConcurrencyMode.Multiple is by definition also reentrant, because no lock is held during the callout.However, unlike a reentrant service, which is inherently threadsafe, a service configured with ConcurrencyMode.Multiple must provide for its own synchronization (for example, by locking the instance during every call, as explained previously). It is up to the developer of such a service to decide if it should release the lock before calling out to avoid a reentrancy deadlock. ConcurrencyMode.Multiple本身也是可重入的
可重入不是说两个线程可以并发调用该方法, 它仍然是single模式, 后一个会等前一个执行完之后再进入
可重入是说, 在方法内部调用其他Service或者回调, 当Service或者回调返回时, 仍然能够回到该Context中. 如果不能返回, 就是死锁.
上一篇笔记中提到, 即使Callback中加了ConcurrencyMode.Reentrant, 仍有可能发生死锁.
Instances and Concurrent Access
Using the same proxy, a single client can issue multiple concurrent calls to a service. The client can use multiple threads to invoke calls on the service, or it can issue oneway calls in rapid succession on the same thread. In both of these cases, whether the calls from the same client are processed concurrently is the product of the service’s configured instancing mode, the service’s concurrency mode, and the configured delivery mode (that is, the transport session). The following discussion applies equally to request-reply and one-way calls.
Per-Call Services
In the case of a per-call service, if there is no transport-level session, concurrent processing of calls is allowed. Calls are dispatched as they arrive, each to a new instance, and execute concurrently. This is the case regardless of the service concurrency mode.
If the service is configured with ConcurrencyMode.Single, concurrent processing of the pending calls is not allowed, and the calls are dispatched one at a time.
If the service is configured with ConcurrencyMode.Multiple, concurrent processing is allowed. Calls are dispatched as they arrive, each to a new instance, and execute concurrently. it is a good idea to configure a per-call service with Concurrency Mode.Multiple—the instance itself will still be thread-safe (so you will not incur the synchronization liability), yet you will allow concurrent calls from the same client
When the service is configured with ConcurrencyMode.Reentrant, if the service does not call out, it behaves similarly to a service configured with ConcurrencyMode.Single. If the service does call out, the next call is allowed in, and the returning call has to negotiate the lock like all other pending calls
Sessionful and Singleton Services
In the case of a sessionful or a singleton service, the configured concurrency mode alone governs the concurrent execution of pending calls. If the service is configured with ConcurrencyMode.Single, calls will be dispatched to the service instance one at a time, and pending calls will be placed in a queue. You should avoid lengthy processing of calls, because it may risk call timeouts.
If the service instance is configured with ConcurrencyMode.Multiple, concurrent processing of calls from the same client is allowed. Calls will be executed by the service instance as fast as they come off the channel (up to the throttle limit). Of course, as is always the case with a stateful unsynchronized service instance, you must synchronize access to the service instance or risk state corruption.
If the service instance is configured with ConcurrencyMode.Reentrant, it behaves just as it would with ConcurrencyMode.Single. However, if the service calls out, the next call is allowed to execute. You must follow the guidelines discussed previously regarding programming in a reentrant environment.